#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/ide.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/platform_device.h>
#include <linux/irq.h>
#include <linux/atomic.h>
#include <linux/poll.h>

#define SCR04_NAME  "scr04"
#define SCR04_CNT   1

struct scr04_dev{
    dev_t devid;
    struct device *device;
    struct class *class;
    struct cdev cdev;
    struct device_node *node;
    int irqnum;
    int gpio_trig;
    int gpio_echo;
    int minor;
    int major;
    atomic_t scr04_atomic;
    wait_queue_head_t wq;
};
struct scr04_dev scr04dev;

static int scr04_data_ns = 0;

static irqreturn_t scr04_handler(int irq, void *dev_iq)
{
   struct scr04_dev *dev = (struct scr04_dev*)dev_iq;
   if(gpio_get_value(scr04dev.gpio_echo)){
        scr04_data_ns = ktime_get_ns();
   }
   else{
        scr04_data_ns = ktime_get_ns() - scr04_data_ns;
        wake_up(&dev->wq);
   }
   return IRQ_RETVAL(IRQ_HANDLED);
}

static int scr04_open(struct inode *inode, struct file *filp)
{
    filp->private_data = &scr04dev;
    if(!atomic_dec_and_test(&scr04dev.scr04_atomic)){
        atomic_inc(&scr04dev.scr04_atomic);
        return -EBUSY;
    }
    gpio_direction_input(scr04dev.gpio_echo);
    gpio_direction_output(scr04dev.gpio_trig,0);

    return 0;
}


static ssize_t scr04_read(struct file *filp, char __user *buf,size_t cnt, loff_t *offt)
{
    int ret;
    int timeout;
    struct scr04_dev *dev = filp->private_data;
    unsigned long flags;
    local_irq_save(flags);

    printk("write\r\n");
    gpio_set_value(dev->gpio_trig,1);
    udelay(40);
    gpio_set_value(dev->gpio_trig,0);
    local_irq_restore(flags);

    timeout = wait_event_interruptible_timeout(scr04dev.wq,scr04_data_ns,HZ);
    if(!timeout) return EINVAL;

    if(copy_to_user(buf,&scr04_data_ns,sizeof(scr04_data_ns) > 4 ? 4 : cnt)){
        ret = EINVAL;
    }
    else{
        ret = cnt;
    }
    scr04_data_ns = 0;
    return ret;
}

static unsigned int scr04_poll(struct file *filp, struct poll_table_struct *wait)
{
    return 0;
}

static int scr04_release(struct inode *inode, struct file *filp)
{
    struct scr04_dev *dev = filp->private_data;
    atomic_set(&dev->scr04_atomic,1);
    
    printk("user space exit\r\n");
    return 0;
}


static struct file_operations scr04_operations = {
    .open = scr04_open,
    .read = scr04_read,
    .poll = scr04_poll,
    .release = scr04_release,
};

static int scr04_probe(struct platform_device *dev)
{
    int ret = 0;
    atomic_set(&scr04dev.scr04_atomic,1);
    printk("the driver is compatible to the device!\r\n");

    scr04dev.node = of_find_node_by_path("/scr04");
    if(scr04dev.node == NULL){
        printk("/scr04 not find\r\n");
        return -EINVAL;
    }

    scr04dev.gpio_trig = of_get_named_gpio(scr04dev.node,"trig-gpio",0);
    if(scr04dev.gpio_trig < 0){
        printk("get gpio-trig err\r\n");
        return -EINVAL;
    }

    scr04dev.gpio_echo = of_get_named_gpio(scr04dev.node,"echo-gpio",0);
    if(scr04dev.gpio_echo <0){
        printk("get gpio-echo err\r\n");
        return -EINVAL;
    }

    ret = gpio_request(scr04dev.gpio_trig,"gpio_trig");
    if(ret){
        gpio_free(scr04dev.gpio_trig);
        printk("request gpio_trig err\r\n");
    }
    ret = gpio_request(scr04dev.gpio_echo,"gpio_echo");
    if(ret){
        gpio_free(scr04dev.gpio_echo);
        printk("request gpio_echo err\r\n");
    }
    printk("pin-trig = %d,pin-echo = %d\r\n",scr04dev.gpio_trig,scr04dev.gpio_echo);
    gpio_direction_output(scr04dev.gpio_trig,0);
    gpio_direction_input(scr04dev.gpio_echo);


    if(scr04dev.major){
        scr04dev.devid = MKDEV(scr04dev.major,0);
        ret = register_chrdev_region(scr04dev.devid,0,SCR04_NAME);
        if(ret < 0){
            printk("request region\r\n");
            ret = -EINVAL;
            goto register_fail;
        }
    }
    else{
        ret = alloc_chrdev_region(&scr04dev.devid,0,1,SCR04_NAME);
        if(ret < 0){
            printk("request region\r\n");
            ret = -EINVAL;
            goto register_fail;
        }
        scr04dev.major = MAJOR(scr04dev.devid);
        scr04dev.minor = MINOR(scr04dev.devid);
    }
    
    scr04dev.cdev.owner = THIS_MODULE;
    cdev_init(&scr04dev.cdev,&scr04_operations);
    if(cdev_add(&scr04dev.cdev,scr04dev.devid,SCR04_CNT)){
        printk("cdev add failer\r\n");
        goto cdev_fail;
        ret = -EINVAL;
    }
    
    scr04dev.class = class_create(THIS_MODULE,SCR04_NAME);
    if(IS_ERR(scr04dev.class)){
        goto class_fail;
        ret = PTR_ERR(scr04dev.class);
    }

    scr04dev.device = device_create(scr04dev.class,NULL,scr04dev.devid,NULL,SCR04_NAME);
    if(IS_ERR(scr04dev.device)){
        printk("device create failed\r\n");
        goto device_fail;
        ret = PTR_ERR(scr04dev.device);
    }

    scr04dev.irqnum = irq_of_parse_and_map(scr04dev.node,0);
    ret = request_irq(scr04dev.irqnum, scr04_handler, IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,
	    "eho1", &scr04dev);
    if(ret < 0){
        printk("irq %d request failed !\r\n",scr04dev.irqnum);
        ret = EINVAL;
        goto irq_fail;        
    }

    init_waitqueue_head(&scr04dev.wq);

    return 0;

irq_fail:
    free_irq(scr04dev.irqnum,&scr04dev);

device_fail:
    device_destroy(scr04dev.class,scr04dev.devid);

class_fail:
    class_destroy(scr04dev.class);

cdev_fail:
    cdev_del(&scr04dev.cdev);

register_fail:
    unregister_chrdev_region(scr04dev.devid,SCR04_CNT);

return ret;
}

static int scr04_remove(struct platform_device *dev)
{
    device_destroy(scr04dev.class,scr04dev.devid);
    class_destroy(scr04dev.class);
    cdev_del(&scr04dev.cdev);
    unregister_chrdev_region(scr04dev.devid,SCR04_CNT);

    free_irq(scr04dev.irqnum,&scr04dev);
    gpio_free(scr04dev.gpio_echo);
    gpio_free(scr04dev.gpio_trig);
    printk("remove\r\n");
    return 0;
}

static const struct of_device_id scr04_of_match [] = {
    {.compatible = "gary-scr04"},
    { }
};

static struct platform_driver scr04_driver = {
    .driver = {
        .name = "imx6ull-scr04",
        .of_match_table = scr04_of_match,
    },
    .probe = scr04_probe,
    .remove = scr04_remove,
};

static int __init scr04driver_init(void)
{
    return platform_driver_register(&scr04_driver);
}

static void __exit scr04driver_exit(void)
{
    platform_driver_unregister(&scr04_driver);
}


module_init(scr04driver_init);
module_exit(scr04driver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("GARY");